package epam.bank.repository;

import epam.bank.api.repository.IGenericCRUDRepository;
import epam.bank.domain.DomainObject;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.orm.jpa.JpaCallback;
import org.springframework.orm.jpa.JpaTemplate;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceException;
import javax.persistence.Query;
import java.util.ArrayList;
import java.util.List;

/**
 * Created with IntelliJ IDEA.
 * User: dima
 * Date: 09.03.13
 * Time: 22:33
 * To change this template use File | Settings | File Templates.
 */
public abstract class GenericCRUDRepositoryImpl<T extends DomainObject> implements IGenericCRUDRepository<T> {

    private static final String QUERY_COUNT_ALL = "SELECT COUNT(x) FROM %s x";
    private static final String QUERY_SELECT_ALL = "SELECT x FROM %s x";

    @Autowired
    protected JpaTemplate jpaTemplate;

    protected Class<T> persistentClass;

    public GenericCRUDRepositoryImpl(Class<T> persistentClass) {
        super();
        this.persistentClass = persistentClass;
    }

    public JpaTemplate getJpaTemplate() {
        return jpaTemplate;
    }

    public void setJpaTemplate(JpaTemplate jpaTemplate) {
        this.jpaTemplate = jpaTemplate;
    }

    protected abstract void beforeEntityDelete(T entity);

    /**
     * Select-request
     */
    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    protected <REZ> REZ executeQuery(String queryOrQueryName, boolean namedQuery, boolean singleResult, Object... parameters) {
        REZ result;
        List<?> list;

        if (namedQuery) {
            list = jpaTemplate.findByNamedQuery(queryOrQueryName, parameters);
        } else {
            list = jpaTemplate.find(queryOrQueryName, parameters);
        }

        if (singleResult) {
            if (list != null && list.size() != 0) {
                result = (REZ) list.get(0);
            } else {
                result = null;
            }
        } else {
            result = (REZ) list;
        }

        return result;
    }

    /**
     * Delete/update-query
     *
     * @param
     * @return
     * @throws IllegalArgumentException
     */
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    protected int executeUpdateQuery(final String queryOrQueryName, final boolean namedQuery, final Object... parameters) {
        Object rez = jpaTemplate.execute(new JpaCallback<Object>() {
            @Override
            public Object doInJpa(EntityManager em) throws PersistenceException {
                Query query;
                if (namedQuery) {
                    query = em.createNamedQuery(queryOrQueryName);
                } else {
                    query = em.createQuery(queryOrQueryName);
                }

                if (parameters.length > 0) {
                    for (int i = 0; i < parameters.length; i++) {
                        query.setParameter(i + 1, parameters[i]);
                    }
                }

                return Integer.valueOf(query.executeUpdate());
            }
        });
        return ((Integer) rez).intValue();
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    @Override
    public T save(T entity) throws IllegalArgumentException {
        if (entity == null) {
            throw new IllegalArgumentException("Entity for saving cannot be null!");
        }

        T savedEntity = null;

        if (entity.getId() == null) {
            jpaTemplate.persist(entity);
            savedEntity = entity;
        } else {
            savedEntity = jpaTemplate.merge(entity);
        }

        return savedEntity;
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    @Override
    public void delEntity(Long id) throws IllegalArgumentException {
        if (id == null) {
            throw new IllegalArgumentException("id is null");
        }

        T savedEntity = getEntityById(id);

        if (savedEntity != null) {
            delEntity(savedEntity);
        }
    }

    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    @Override
    public void delEntity(T entity) throws IllegalArgumentException {
        if (entity == null) {
            throw new IllegalArgumentException("entity is null");
        }

        beforeEntityDelete(entity);

        if (!jpaTemplate.contains(entity)) {
            T attachedEntity = jpaTemplate.merge(entity);
            jpaTemplate.remove(attachedEntity);
        } else {
            jpaTemplate.remove(entity);
        }
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    @Override
    public T getEntityById(Long id) throws IllegalArgumentException {
        if (id == null) {
            throw new IllegalArgumentException("id is null");
        }

        try {
            T savedEntity = jpaTemplate.find(persistentClass, id);

            if ((savedEntity == null) || (savedEntity.getId() == null)) {
                StringBuilder mess = new StringBuilder("Entity by id");
                mess.append(id).append(" was not found");

                return null;
            }

            return savedEntity;

        } catch (Exception e) {
            return null;
        }
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    @Override
    public List<T> getAllEntities() {
        List<T> list = executeQuery(String.format(QUERY_SELECT_ALL, persistentClass.getSimpleName()), false, false);
        if (list != null) {
            return list;
        } else {
            return new ArrayList<T>();
        }
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    @Override
    public long getAllEntitiesCount() {
        return executeQuery(String.format(QUERY_COUNT_ALL, persistentClass.getSimpleName()), false, true);
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    @Override
    public List<T> getAllSorted(String propertySortBy, boolean asc) throws IllegalArgumentException {

        try {
            String query = String.format(QUERY_SELECT_ALL,
                    persistentClass.getSimpleName());

            String newQuery = addSortPropertyToQuery(query, propertySortBy, asc);

            return executeQuery(newQuery, false, false);
        } catch (Exception e) {
            //���
            return new ArrayList<T>();
        }
    }

    protected String addSortPropertyToQuery(String query,
                                            String propertySortBy, boolean asc) {
        StringBuilder sb = new StringBuilder();
        sb.append(query);
        sb.append(" ");
        sb.append("order by ");
        sb.append(propertySortBy);

        if (asc) {
            sb.append(" asc");
        } else {
            sb.append(" desc");
        }

        String newQuery = sb.toString();
        return newQuery;
    }
}
